﻿using System.Collections.Generic;
using System.Linq;
using RW.Data.Graph;
using RW.Data.Objects;

namespace RW.Data
{
    public class Solver
    {
        private Problem problem;

        public Solver(Problem problem)
        {
            this.problem = problem;
        }

        public MultiGraph CreateMultiGraph()
        {
            MultiGraph graph = new MultiGraph();

            this.CreateGraphVertices(graph);
            this.CreateGraphEdges(graph);
            graph.SSet = this.GenerateSSet(graph);

            return graph;
        }

        private void CreateGraphVertices(MultiGraph graph)
        {
            int varCombinationsCount = (int)System.Math.Pow(2, this.problem.Variables.Count);
            for (int i = 0; i < varCombinationsCount; ++i)
            {
                State state = this.CreateStateFromNumber(i);

                state.IsValid = this.problem.CheckIntegrityConditions(state);
                graph.AddVertex(state);
            }
        }

        private State CreateStateFromNumber(int no)
        {
            State result = new State();

            for (int i = 0; i < this.problem.Variables.Count; ++i)
                result.Add(this.problem.Variables[i], (no & (1 << i)) != 0);

            return result;
        }

        private void CreateGraphEdges(MultiGraph graph)
        {
            foreach (State state in graph.Vertices.Where(v => this.problem.CheckIntegrityConditions(v)))
            {
                foreach (Action action in this.problem.Actions)
                {
                    if (action.CheckIfPossible(state))
                    {
                        foreach (ActionStateResult result in action.GetMinResult(state))
                        {
                            if (this.problem.CheckIntegrityConditions(result.State))
                            {
                                State state2 = graph.Vertices.First(q => q.Equals(result.State));
                                if (state2.Equals(state))
                                    state.Loops.Add(new MultiGraphEdge(state, state2, action.Name, action.GetCost(state)));

                                graph.AddEdge(new MultiGraphEdge(state, state2, action.Name, action.GetCost(state)));
                            }
                        }
                    }
                }
            }
        }

        private HashSet<State> GenerateSSet(MultiGraph graph)
        {
            HashSet<State> result = new HashSet<State>();
            foreach (State state in graph.Vertices)
            {
                if (this.problem.IsInitialState(state))
                {
                    bool shouldBeAddedToResult = true;
                    foreach (Observation observation in this.problem.Observations)
                    {
                        bool compatibleWithObservation;
                        List<State> states = new List<State>();
                        states.Add(state);

                        foreach (Action action in observation.AfterActions)
                        {
                            List<State> newStates = new List<State>();
                            foreach (State afterState in states)
                            {
                                if (action.CheckIfPossible(afterState))
                                    newStates.AddRange(action.GetMinResult(afterState).Select(s => s.State).Where(s => this.problem.CheckIntegrityConditions(s)));
                            }

                            states = newStates;
                        }

                        if (observation.IsObservable)
                            compatibleWithObservation = states.Any(finalState => observation.Result.Execute(finalState));
                        else
                            compatibleWithObservation = states.All(finalState => observation.Result.Execute(finalState));

                        if (!compatibleWithObservation)
                        {
                            shouldBeAddedToResult = false;
                            break;
                        }
                    }

                    if (shouldBeAddedToResult)
                    {
                        result.Add(state);
                        state.IsInitial = true;
                    }
                }
            }

            return result;
        }

    }
}
